home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / sbin / pam-auth-update < prev    next >
Text File  |  2008-10-16  |  19KB  |  681 lines

  1. #!/usr/bin/perl -w
  2.  
  3. # pam-auth-update: update /etc/pam.d/common-* from /usr/share/pam-configs
  4. #
  5. # Update the /etc/pam.d/common-* files based on the per-package profiles
  6. # provided in /usr/share/pam-configs/ taking into consideration user's
  7. # preferences (as determined via debconf prompting).
  8. #
  9. # Written by Steve Langasek <steve.langasek@canonical.com>
  10. #
  11. # Copyright (C) 2008 Canonical Ltd.
  12. #
  13. # This program is free software; you can redistribute it and/or modify
  14. # it under the terms of version 3 of the GNU General Public License as
  15. # published by the Free Software Foundation.
  16. #
  17. # # This program is distributed in the hope that it will be useful,
  18. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20. # GNU General Public License for more details.
  21. #
  22. # You should have received a copy of the GNU General Public License
  23. # along with this program; if not, write to the Free Software
  24. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
  25. # USA.
  26.  
  27. use strict;
  28. use Debconf::Client::ConfModule ':all';
  29. use IPC::Open2 'open2';
  30.  
  31. version('2.0');
  32. my $capb=capb('backup');
  33.  
  34. my $inputdir = '/usr/share/pam-configs';
  35. my $template = 'libpam-runtime/profiles';
  36. my $errtemplate = 'libpam-runtime/conflicts';
  37. my $overridetemplate = 'libpam-runtime/override';
  38. my $confdir = '/etc/pam.d';
  39. my $savedir = '/var/lib/pam';
  40. my (%profiles, @sorted, @enabled, @conflicts, %removals);
  41. my $force = 0;
  42. my $priority = 'high';
  43. my %md5sums = (
  44.     'auth' => [
  45.         '1fd1e8e87cef1c13898410d830229122',
  46.         '47d644e88e541aac55da46bc99c2bf3e',
  47.     ],
  48.     'account' => [
  49.         '8a29dc79152ce8441aa90a8f8650d076',
  50.         'e3a19a1729166046c0bd2222e70142e6',
  51.     ],
  52.     'password' => [
  53.         '9ba753d0824276b44bcadfee1f87b6bc',
  54.         '86180c1552203d9b58582cf547309d01',
  55.         '3532cbabf533d59f0b64218ad82f1446',
  56.         '6d14efb5c3306c6896b9acf434932808',
  57.         '21e152635b16ff793e4a7941396545dd',
  58.     ],
  59.     'session' => [
  60.         '4a25673e8b36f1805219027d3be02cd2',
  61.         '1bd2f3e86f552c57f5ee013b93ffca2b',
  62.         '06cffe624c9bb7d9a7b5891c8a0f94b2',
  63.         '1a1bda3d417991dd366984ca7382f787',
  64.     ],
  65. );
  66.  
  67. opendir(DIR, $inputdir) || die "could not open config directory: $!";
  68. while (my $profile = readdir(DIR)) {
  69.     next if ($profile eq '.' || $profile eq '..');
  70.     %{$profiles{$profile}} = parse_pam_profile($inputdir . '/' . $profile);
  71. }
  72. closedir DIR;
  73.  
  74. # use a '--force' arg to specify that /etc/pam.d should be overwritten; 
  75. # used only on upgrades where the postinst has already determined that the
  76. # checksums match.  Module packages other than libpam-runtime itself must
  77. # NEVER use this option!  Document with big skullses and crossboneses!  It
  78. # needs to be exposed for libpam-runtime because that's the package that
  79. # decides whether we have a pristine config to be converted, and knows
  80. # whether the version being upgraded from is one for which the conversion
  81. # should be done.
  82.  
  83. while ($#ARGV >= 0) {
  84.     my $opt = shift;
  85.     if ($opt eq '--force') {
  86.         $force = 1;
  87.     } elsif ($opt eq '--package') {
  88.         $priority = 'medium';
  89.     } elsif ($opt eq '--remove') {
  90.         while ($#ARGV >= 0) {
  91.             last if ($ARGV[0] =~ /^--/);
  92.             $removals{shift @ARGV} = 1;
  93.         }
  94.         # --remove implies --package
  95.         $priority = 'medium' if (keys(%removals));
  96.     }
  97. }
  98.  
  99. x_loadtemplatefile('/var/lib/dpkg/info/libpam-runtime.templates','libpam-runtime');
  100.  
  101. # always sort by priority, so we have consistency and don't have to
  102. # shuffle later
  103. @sorted = sort { $profiles{$b}->{'Priority'} <=> $profiles{$a}->{'Priority'}
  104.                  || $b cmp $a }
  105.                keys(%profiles);
  106. # If we're being called for package removal, filter out those options here
  107. @sorted = grep { !$removals{$_} } @sorted;
  108.  
  109. subst($template, 'profile_names', join(', ',@sorted));
  110. subst($template, 'profiles',
  111.     join(', ', map { $profiles{$_}->{'Name'} } @sorted));
  112.  
  113. my $diff = diff_profiles($confdir,$savedir);
  114.  
  115. if ($diff) {
  116.     @enabled = grep { !$removals{$_} } @{$diff->{'mods'}};
  117. } else {
  118.     @enabled = split(/, /,get($template));
  119. }
  120.  
  121. # an empty module set is an error, so grab the defaults instead
  122. if (!@enabled) {
  123.     @enabled = grep { $profiles{$_}->{'Default'} eq 'yes' } @sorted;
  124.     $priority = 'high' unless ($force);
  125. } elsif (-e $savedir . '/seen') {
  126.     # filter out any options that are no longer available for any reason
  127.     @enabled = grep { $profiles{$_} } @enabled;
  128.     # add any previously-unseen configs
  129.     my %seen;
  130.     open(SEEN,$savedir . '/seen');
  131.     while (<SEEN>) {
  132.         chomp;
  133.         $seen{$_} = 1;
  134.     }
  135.     close(SEEN);
  136.     push(@enabled,
  137.          grep { $profiles{$_}->{'Default'} eq 'yes' && !$seen{$_} } @sorted);
  138.     @enabled = sort { $profiles{$b}->{'Priority'} <=> $profiles{$a}->{'Priority'}
  139.                       || $b cmp $a }
  140.                     @enabled;
  141.     my $prev = '';
  142.     @enabled = grep { $_ ne $prev && (($prev) = $_) } @enabled;
  143. }
  144.  
  145. fset($template,'seen','false');
  146. set($template,join(', ', @enabled));
  147.  
  148. # if diff_profiles() fails, and we weren't passed a 'force' argument
  149. # (because this isn't an upgrade from an old version, or the checksum
  150. # didn't match, or we're being called by some other module package), prompt
  151. # the user whether to override.  If the user declines (the default), we
  152. # never again manage this config unless manually called with '--force'.
  153. if (!$diff && !$force) {
  154.     input('high',$overridetemplate);
  155.     go();
  156.     $force = 1 if (get($overridetemplate) eq 'true');
  157. }
  158.  
  159. if (!$diff && !$force) {
  160.     print STDERR <<EOF;
  161.  
  162. pam-auth-update: Local modifications to /etc/pam.d/common-*, not updating.
  163. pam-auth-update: Run pam-auth-update --force to override.
  164.  
  165. EOF
  166.     exit;
  167. }
  168.  
  169. do {
  170.     @conflicts = ();
  171.     input($priority,$template);
  172.     go();
  173.  
  174.     @enabled = split(/, /, get($template));
  175.  
  176.     # in case of conflicts, automatically unset the lower priority
  177.     # item of each pair
  178.     foreach my $elem (@enabled)
  179.     {
  180.         for (my $i=$#enabled; $i >= 0; $i--)
  181.         {
  182.             my $conflict = $enabled[$i];
  183.             if ($profiles{$elem}->{'Conflicts'}->{$conflict}) {
  184.                 splice(@enabled,$i,1);
  185.                 my $desc = $profiles{$elem}->{'Name'}
  186.                     . ', ' . $profiles{$conflict}->{'Name'};
  187.                 push(@conflicts,$desc);
  188.             }
  189.         }
  190.     }
  191.     if (@conflicts) {
  192.         subst($errtemplate, 'conflicts', join("\n", @conflicts));
  193.         input('high',$errtemplate);
  194.     }
  195.     fset($template,'seen','false');
  196.     set($template, join(', ', @enabled));
  197. } while (@conflicts);
  198.  
  199. # the decision has been made about what configs to use, so even if
  200. # something fails after this, we shouldn't go munging the default
  201. # options again.  Save the list of known configs to /var/lib/pam.
  202. open(SEEN,"> $savedir/seen");
  203. for my $i (@sorted) {
  204.     print SEEN "$i\n";
  205. }
  206. close(SEEN);
  207.  
  208. # @enabled now contains our list of profiles to use for piecing together
  209. # a config
  210. # we have:
  211. # - templates into which we insert the specialness
  212. # - magic comments denoting the beginning and end of our managed block;
  213. #   looking at only the functional config lines would potentially let us
  214. #   handle more cases, at the expense of much greater complexity, so
  215. #   pass on this at least for the first round
  216. # - a representation of the autogenerated config stored in /var/lib/pam,
  217. #   that we can diff against in order to account for changed options or
  218. #   manually dropped modules
  219. # - a hash describing the local modifications the user has made to the
  220. #   config; these are always preserved unless manually overridden with
  221. #   the --force option
  222.  
  223. write_profiles(\%profiles, \@enabled, $confdir, $savedir, $diff, $force);
  224.  
  225.  
  226. # take a single line from a stock config, and merge it with the
  227. # information about local admin edits
  228. sub merge_one_line
  229. {
  230.     my ($line,$diff,$count) = @_;
  231.     my (@opts,$modline);
  232.  
  233.     my ($adds,$removes);
  234.  
  235.     $line =~ /^((\[[^]]+\]|\w+)\s+\S+)\s*(.*)/;
  236.  
  237.     @opts = split(/\s+/,$3);
  238.     $modline = $1;
  239.     $modline =~ s/end/$count/g;
  240.     if ($diff) {
  241.         my $mod = $modline;
  242.         $mod =~ s/[0-9]+//g;
  243.         $adds = \%{$diff->{'add'}{$mod}};
  244.         $removes = \%{$diff->{'remove'}{$mod}};
  245.     } else {
  246.         $adds = $removes = undef;
  247.     }
  248.  
  249.     for (my $i = 0; $i <= $#opts; $i++) {
  250.         if ($adds->{$opts[$i]}) {
  251.             delete $adds->{$opts[$i]};
  252.         }
  253.         if ($removes->{$opts[$i]}) {
  254.             splice(@opts,$i,1);
  255.             $i--;
  256.         }
  257.     }
  258.     return $modline . " " . join(' ',@opts,keys(%{$adds})) . "\n";
  259. }
  260.  
  261. # create a single PAM config from the indicated template and selections,
  262. # writing to a new file
  263. sub create_from_template
  264. {
  265.     my($template,$dest,$profiles,$enabled,$diff,$type) = @_;
  266.     my $state = 0;
  267.     my $uctype = ucfirst($type);
  268.  
  269.     open(INPUT,$template) || return 0;
  270.     open(OUTPUT,">$dest") || return 0;
  271.  
  272.     while (<INPUT>) {
  273.         if ($state == 1) {
  274.             if (/^# here's the fallback if no module succeeds/) {
  275.                 print OUTPUT;
  276.                 $state++;
  277.             }
  278.             next;
  279.         }
  280.         if ($state == 3) {
  281.             if (/^# end of pam-auth-update config/) {
  282.                 print OUTPUT;
  283.                 $state++;
  284.             }
  285.             next;
  286.         }
  287.  
  288.         print OUTPUT;
  289.  
  290.         my ($pattern,$val);
  291.         if ($state == 0) {
  292.             $pattern = '^# here are the per-package modules \(the "Primary" block\)';
  293.             $val = 'Primary';
  294.         } elsif ($state == 2) {
  295.             $pattern = '^# and here are more per-package modules \(the "Additional" block\)';
  296.             $val = 'Additional';
  297.         } else {
  298.             next;
  299.         }
  300.  
  301.         if (/$pattern/) {
  302.             my $i = 0;
  303.             my $count = 0;
  304.             # first we need to get a count of lines that we're
  305.             # going to output, so we can fix up the jumps correctly
  306.             for my $mod (@{$enabled}) {
  307.                 my $output;
  308.                 next if (!$profiles->{$mod}{$uctype . '-Type'});
  309.                 next if $profiles->{$mod}{$uctype . '-Type'} ne $val;
  310.                 if ($i++ == 0
  311.                     && $profiles->{$mod}{$uctype . '-Initial'})
  312.                 {
  313.                     $output = $profiles->{$mod}{$uctype . '-Initial'};
  314.                 } else {
  315.                     $output = $profiles->{$mod}{$uctype};
  316.                 }
  317.                 # bypasses a perl warning about @_, sigh
  318.                 my @tmparr = split("\n+",$output);
  319.                 $count += @tmparr;
  320.             }
  321.  
  322.             # in case anything tries to jump in the 'additional'
  323.             # block, let's try not to jump off the stack...
  324.             $count-- if ($val eq 'Additional');
  325.  
  326.             # no primary block, so output a stock pam_permit line
  327.             # to keep the stack intact
  328.             if ($val eq 'Primary' && $count == 0)
  329.             {
  330.                 print OUTPUT "$type\t[default=1]\t\t\tpam_permit.so\n";
  331.             }
  332.  
  333.             $i = 0;
  334.             for my $mod (@{$enabled}) {
  335.                 my $output;
  336.                 my @output;
  337.                 next if (!$profiles->{$mod}{$uctype . '-Type'});
  338.                 next if $profiles->{$mod}{$uctype . '-Type'} ne $val;
  339.                 if ($i++ == 0
  340.                     && $profiles->{$mod}{$uctype . '-Initial'})
  341.                 {
  342.                     $output = $profiles->{$mod}{$uctype . '-Initial'};
  343.                 } else {
  344.                     $output = $profiles->{$mod}{$uctype};
  345.                 }
  346.                 for my $line (split("\n",$output)) {
  347.                     $line = merge_one_line($line,$diff,
  348.                                            $count);
  349.                     print OUTPUT "$type\t$line";
  350.                     $count--;
  351.                 }
  352.             }
  353.             $state++;
  354.         }
  355.     }
  356.     close(INPUT);
  357.     close(OUTPUT);
  358.  
  359.     if ($state < 4) {
  360.         unlink($dest);
  361.         return 0;
  362.     }
  363.     return 1;
  364. }
  365.  
  366. # take a template file, strip out everything between the markers, and
  367. # return the md5sum of the remaining contents.  Used for testing for
  368. # local modifications of the boilerplate.
  369. sub get_template_md5sum
  370. {
  371.     my($template) = @_;
  372.     my $state = 0;
  373.  
  374.     open(INPUT,$template) || return '';
  375.     my($md5sum_fd,$output_fd);
  376.     my $pid = open2($md5sum_fd, $output_fd, 'md5sum');
  377.     return '' if (!$pid);
  378.  
  379.     while (<INPUT>) {
  380.         if ($state == 1) {
  381.             if (/^# here's the fallback if no module succeeds/) {
  382.                 print $output_fd $_;
  383.                 $state++;
  384.             }
  385.             next;
  386.         }
  387.         if ($state == 3) {
  388.             if (/^# end of pam-auth-update config/) {
  389.                 print $output_fd $_;
  390.                 $state++;
  391.             }
  392.             next;
  393.         }
  394.  
  395.         print $output_fd $_;
  396.  
  397.         my ($pattern,$val);
  398.         if ($state == 0) {
  399.             $pattern = '^# here are the per-package modules \(the "Primary" block\)';
  400.         } elsif ($state == 2) {
  401.             $pattern = '^# and here are more per-package modules \(the "Additional" block\)';
  402.         } else {
  403.             next;
  404.         }
  405.  
  406.         if (/$pattern/) {
  407.             $state++;
  408.         }
  409.     }
  410.     close(INPUT);
  411.     close($output_fd);
  412.     my $md5sum = <$md5sum_fd>;
  413.     close($md5sum_fd);
  414.     waitpid $pid, 0;
  415.  
  416.     $md5sum = (split(/\s+/,$md5sum))[0];
  417.     return $md5sum;
  418. }
  419.  
  420. # merge a set of module declarations into a set of new config files,
  421. # using the information returned from diff_profiles().
  422. sub write_profiles
  423. {
  424.     my($profiles,$enabled,$confdir,$savedir,$diff,$force) = @_;
  425.  
  426.     if (! -d $savedir) {
  427.         mkdir($savedir);
  428.     }
  429.         
  430.     # because we can't atomically replace both /var/lib/pam/$foo and
  431.     # /etc/pam.d/common-$foo at the same time, take steps to make this
  432.     # somewhat robust
  433.     for my $type ('auth','account','password','session') {
  434.         my $target = $confdir . '/common-' . $type;
  435.         my $template = $target;
  436.         my $dest = $template . '.pam-new';
  437.  
  438.         my $diff = $diff;
  439.         if ($diff) {
  440.             $diff = \%{$diff->{$type}};
  441.         }
  442.  
  443.         # Detect if the template is unmodified, and if so, use
  444.         # the version from /usr/share.  Depends on knowing the
  445.         # md5sums of the originals.
  446.         my $md5sum = get_template_md5sum($template);
  447.         for my $i (@{$md5sums{$type}}) {
  448.             if ($md5sum eq $i) {
  449.                 $template = '/usr/share/pam/common-' . $type;
  450.                 last;
  451.             }
  452.         }
  453.  
  454.         # first, write out the new config
  455.         if (!create_from_template($template,$dest,$profiles,$enabled,
  456.                                   $diff,$type))
  457.         {
  458.             if (!$force) {
  459.                 return 0;
  460.             }
  461.             $template = '/usr/share/pam/common-' . $type;
  462.             if (!create_from_template($template,$dest,$profiles,
  463.                                       $enabled,$diff,$type))
  464.             {
  465.                 return 0;
  466.             }
  467.         }
  468.  
  469.         # then write out the saved config
  470.         if (!open(OUTPUT, "> $savedir/$type.new")) {
  471.             unlink($dest);
  472.             return 0;
  473.         }
  474.         my $i = 0;
  475.         my $uctype = ucfirst($type);
  476.         for my $mod (@{$enabled}) {
  477.             my $output;
  478.             next if (!$profiles->{$mod}{$uctype . '-Type'});
  479.             next if ($profiles->{$mod}{$uctype . '-Type'} eq 'Additional');
  480.  
  481.             if ($i++ == 0 && $profiles->{$mod}{$uctype . '-Initial'})
  482.             {
  483.                 $output = $profiles->{$mod}{$uctype . '-Initial'};
  484.             } else {
  485.                 $output = $profiles->{$mod}{$uctype};
  486.             }
  487.             if ($output) {
  488.                 print OUTPUT "Module: $mod\n";
  489.                 print OUTPUT $output . "\n";
  490.             }
  491.         }
  492.  
  493.         # no primary block, so output a stock pam_permit line
  494.         if ($i == 0)
  495.         {
  496.             print OUTPUT "Module: null\n";
  497.             print OUTPUT "[default=1]\t\t\tpam_permit.so\n";
  498.         }
  499.  
  500.         $i = 0;
  501.         for my $mod (@{$enabled}) {
  502.             my $output;
  503.             next if (!$profiles->{$mod}{$uctype . '-Type'});
  504.             next if ($profiles->{$mod}{$uctype . '-Type'} eq 'Primary');
  505.  
  506.             if ($i++ == 0 && $profiles->{$mod}{$uctype . '-Initial'})
  507.             {
  508.                 $output = $profiles->{$mod}{$uctype . '-Initial'};
  509.             } else {
  510.                 $output = $profiles->{$mod}{$uctype};
  511.             }
  512.             if ($output) {
  513.                 print OUTPUT "Module: $mod\n";
  514.                 print OUTPUT $output . "\n";
  515.             }
  516.         }
  517.  
  518.         close(OUTPUT);
  519.  
  520.         # then do the renames, back-to-back
  521.         # we have to use system because File::Copy is in
  522.         # perl-modules, not perl-base
  523.         if (-e "$target" && $force) {
  524.             system('cp','-f',$target,$target . '.pam-old');
  525.         }
  526.         rename($dest,$target);
  527.         rename("$savedir/$type.new","$savedir/$type");
  528.     }
  529.  
  530.     # at the end of a successful write, reset the 'seen' flag and the
  531.     # value of the debconf override question.
  532.     fset($overridetemplate,'seen','false');
  533.     set($overridetemplate,'false');
  534. }
  535.  
  536. # reconcile the current config in /etc/pam.d with the saved ones in
  537. # /var/lib/pam; returns a hash of profile names and the corresponding
  538. # options that should be added/removed relative to the stock config.
  539. # returns false if any of the markers are missing that permit a merge,
  540. # or on any other failure.
  541. sub diff_profiles
  542. {
  543.     my ($sourcedir,$savedir) = @_;
  544.     my (%diff);
  545.  
  546.     @{$diff{'mods'}} = ();
  547.     # Load the saved config from /var/lib/pam, then iterate through all
  548.     # lines in the current config that are in the managed block.
  549.     # If anything fails here, just return immediately since we then
  550.     # have nothing to merge; instead, the caller will decide later
  551.     # whether to force an overwrite.
  552.     for my $type ('auth','account','password','session') {
  553.         my (@saved,$modname);
  554.  
  555.         open(SAVED,$savedir . '/' . $type) || return 0;
  556.         while (<SAVED>) {
  557.             if (/^Module: (.*)/) {
  558.                 $modname = $1;
  559.                 next;
  560.             }
  561.             chomp;
  562.             # trim out the destination of any jumps; this saves
  563.             # us from having to re-parse everything just to fix
  564.             # up the jump lengths, when changes to these will
  565.             # already show up as inconsistencies elsewhere
  566.             s/(\[[^0-9]*)[0-9]+(.*\])/$1$2/g;
  567.             s/(\[.*)end(.*\])/$1$2/g;
  568.             my (@temp) = ($modname,$_);
  569.             push(@saved,\@temp);
  570.         }
  571.         close(SAVED);
  572.  
  573.         my $state = 0;
  574.         my (@prev_opts,$curmod);
  575.  
  576.         open(CURRENT,$sourcedir . '/common-' . $type) || return 0;
  577.         while (<CURRENT>) {
  578.             if ($state == 0) {
  579.                 $state = 1
  580.                    if (/^# here are the per-package modules \(the "Primary" block\)/);
  581.                 next;
  582.             }
  583.             if ($state == 1) {
  584.                 s/^$type\s+//;
  585.                 if (/^# here's the fallback if no module succeeds/) {
  586.                     $state = 2;
  587.                     next;
  588.                 }
  589.             }
  590.             if ($state == 2) {
  591.                 $state = 3
  592.                    if (/^# and here are more per-package modules \(the "Additional" block\)/);
  593.                 next;
  594.             }
  595.             if ($state == 3) {
  596.                 last if (/^# end of pam-auth-update config/);
  597.                 s/^$type\s+//;
  598.             }
  599.  
  600.             my $found = 0;
  601.             my $curopts;
  602.             while (!$found && $#saved >= 0) {
  603.                 my $line;
  604.                 ($modname,$line) = @{$saved[0]};
  605.                 shift(@saved);
  606.                 $line =~ /^((\[[^]]+\]|\w+)\s+\S+)\s*(.*)/;
  607.                 @prev_opts = split(/\s+/,$3);
  608.                 $curmod = $1;
  609.                 # FIXME: the key isn't derived from the config
  610.                 # name, so collisions are possible if more
  611.                 # than one config references the same module
  612.  
  613.                 $_ =~ s/(\[[^0-9]*)[0-9]+(.*\])/$1$2/g;
  614.                 # check if this is a match for the current line
  615.                 if ($_ =~ /^\Q$curmod\E\s*(.*)$/) {
  616.                     $found = 1;
  617.                     $curopts = $1;
  618.                     push(@{$diff{'mods'}},$modname);
  619.                 }
  620.             }
  621.  
  622.             # there's a line in the live config that doesn't
  623.             # correspond to anything from the saved config.
  624.             # treat this as a failure; it's very error-prone
  625.             # to decide what to do with an added line that
  626.             # didn't come from a package.
  627.             return 0 if (!$found);
  628.  
  629.             for my $opt (split(/\s+/,$curopts)) {
  630.                 my $found = 0;
  631.                 for (my $i = 0; $i <= $#prev_opts; $i++) {
  632.                     if ($prev_opts[$i] eq $opt) {
  633.                         $found = 1;
  634.                         splice(@prev_opts,$i,1);
  635.                     }
  636.                 }
  637.                 $diff{$type}{'add'}{$curmod}{$opt} = 1 if (!$found);
  638.             }
  639.             for my $opt (@prev_opts) {
  640.                 $diff{$type}{'remove'}{$curmod}{$opt} = 1;
  641.             }
  642.         }
  643.         close(CURRENT);
  644.  
  645.         # we couldn't parse the config, so the merge fails
  646.         return 0 if ($state < 3);
  647.     }
  648.     return \%diff;
  649. }
  650.  
  651. # simple function to parse a provided config file, in pseudo-RFC822
  652. # format,
  653. sub parse_pam_profile
  654. {
  655.     my ($profile) = $_[0];
  656.     my $fieldname;
  657.     my %profile;
  658.     open(PROFILE, $profile) || die "could not read profile $profile: $!";
  659.     while (<PROFILE>) {
  660.         if (/^(\S+):\s+(.*)$/) {
  661.             $fieldname = $1;
  662.             # compatibility with the first implementation round;
  663.             # "Auth-Final" is now just called "Auth"
  664.             $fieldname =~ s/-Final$//;
  665.             if ($fieldname eq 'Conflicts') {
  666.                 foreach my $elem (split(/, /, $2)) {
  667.                     $profile{'Conflicts'}->{$elem} = 1;
  668.                 }
  669.             } else {
  670.                 $profile{$fieldname} = $2;
  671.             }
  672.         } else {
  673.             chomp;
  674.             $profile{$fieldname} .= "\n$_";
  675.             $profile{$fieldname} =~ s/^[\n\s]+//;
  676.         }
  677.     }
  678.     close(PROFILE);
  679.     return %profile;
  680. }
  681.